package com.jprocms.module.cms.util;

import cn.hutool.core.lang.func.Consumer3;
import com.jprocms.module.cms.dal.base.ICustom;
import com.jprocms.module.cms.dal.dataobject.model.ModelDO;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.lang.Nullable;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Data
@Builder
public class ModelWrapper {

    public static final String CUSTOM_QUERY_PARAM_SUFFIX = "_cus";

    public static final String TYPE_TEXT = "text";
    public static final String TYPE_TEXTAREA = "textarea";
    public static final String TYPE_NUMBER = "number";

    public static final String TYPE_IMAGE_UPLOAD = "imageUpload";
    public static final String TYPE_VIDEO_UPLOAD = "videoUpload";
    public static final String TYPE_FILE_UPLOAD = "fileUpload";
    public static final String TYPE_DATE = "date";
    public static final String TYPE_COLOR = "color";
    public static final String TYPE_RADIO = "radio";
    public static final String TYPE_CHECKBOX = "checkbox";
    public static final String TYPE_SELECT = "select";
    public static final String TYPE_MULTIPLE_SELECT = "multipleSelect";
    public static final String TYPE_CASCADER = "cascader";
    public static final String TYPE_SWITCH = "switch";
    public static final String TYPE_SLIDER = "slider";
    public static final String TYPE_EDITOR = "editor";


    public void disassembleCustoms(Map<String, Object> customs, Consumer3<String, String, String> consumer) {
        customs.forEach((key, value) -> {
            String origKey = key.endsWith(CUSTOM_QUERY_PARAM_SUFFIX) ? key.substring(0, key.lastIndexOf(CUSTOM_QUERY_PARAM_SUFFIX)) : key;
            String type = Optional.ofNullable(modelDO.getFieldMap().get(origKey)).map(Field::getType).orElse(null);
            if (type == null) {
                return;
            }
            if (value instanceof ArrayList) {
                ((ArrayList<?>) value).forEach(val -> consumer.accept(key, type, String.valueOf(val)));
                return;
            }
            Optional.ofNullable(value).map(String::valueOf).filter(StringUtils::isNotBlank)
                    .ifPresent(it -> consumer.accept(key, type, it));
        });
    }

    public Map<String, Object> assembleCustoms(List<? extends ICustom> customs) {
        Map<String, Field> fieldMap = modelDO.getFieldMap();
        Map<String, Object> customMap = new HashMap<>(16);
        fieldMap.forEach((name, field) -> {
            String type = field.getType();
            List<ICustom> list = customs.stream().filter(
                            item -> Objects.equals(item.getName(), name) && compatibleType(item.getAttrType(), type))
                    .collect(Collectors.toList());
            List<ICustom> keyList = customs.stream().filter(
                            item -> Objects.equals(item.getName(), name + CUSTOM_QUERY_PARAM_SUFFIX) && compatibleType(item.getAttrType(), type))
                    .collect(Collectors.toList());
            String value = list.isEmpty() ? null : list.iterator().next().getAttrValue();
            String valueKey = keyList.isEmpty() ? null : keyList.iterator().next().getAttrValue();
            switch (field.getType()) {
                case TYPE_NUMBER:
                case TYPE_SLIDER:
                    if (NumberUtils.isCreatable(value)) {
                        if (field.getPrecision() != null && field.getPrecision() > 0) {
                            customMap.put(name, new BigDecimal(value));
                        } else {
                            customMap.put(name, Long.valueOf(value));
                        }
                    }
                    break;
                case TYPE_DATE:
                    if (value != null) {
                        //日期前端转换
                        customMap.put(name, value);
                    }
                    break;
                case TYPE_SWITCH:
                    if (value != null) {
                        customMap.put(name, Boolean.valueOf(value));
                    }
                    break;
                case TYPE_CHECKBOX:
                case TYPE_MULTIPLE_SELECT:
                    customMap.put(name, list.stream().map(ICustom::getAttrValue).filter(Objects::nonNull)
                            .collect(Collectors.toList()));
                    customMap.put(name + CUSTOM_QUERY_PARAM_SUFFIX, keyList.stream().map(ICustom::getAttrValue).filter(Objects::nonNull)
                            .collect(Collectors.toList()));
                    break;
                default:
                    if (value != null) {
                        customMap.put(name, value);
                    }
                    if (valueKey != null) {
                        customMap.put(name + CUSTOM_QUERY_PARAM_SUFFIX, valueKey);
                    }
            }
        });
        return customMap;
    }

    public void handleCustoms(List<? extends ICustom> customList, CustomHandle handle) {
        Map<String, Field> fieldMap = modelDO.getFieldMap();
        customList.forEach(custom -> {
            String value = custom.getAttrValue();
            if (value == null) {
                return;
            }
            String name = custom.getName();
            Field field = fieldMap.get(name);
            if (field == null) {
                return;
            }
            handle.execute(name, value, field);
        });
    }

    @SuppressWarnings("unchecked")
    public void handleCustoms(Map<String, Object> customs, CustomHandle handle) {
        Map<String, Field> fieldMap = modelDO.getFieldMap();
        customs.forEach((name, obj) -> {
            Field field = fieldMap.get(name);
            if (field == null) {
                return;
            }
            if (obj instanceof List) {
                List<String> list = (List<String>) obj;
                list.forEach(value -> {
                    if (StringUtils.isBlank(value)) {
                        return;
                    }
                    handle.execute(name, value, field);
                });
            } else {
                if (obj != null) {
                    handle.execute(name, String.valueOf(obj), field);
                }
            }
        });
    }


    /**
     * 每一个数组代表一组兼容的数据类型。数值和字符串都作为兼容数据。
     */
    private static final String[][] TYPE_GROUPS = {{TYPE_TEXT, TYPE_TEXTAREA, TYPE_RADIO, TYPE_CHECKBOX, TYPE_SELECT, TYPE_MULTIPLE_SELECT, TYPE_NUMBER, TYPE_SLIDER}};

    private boolean compatibleType(String type1, String type2) {
        if (Objects.equals(type1, type2)) {
            return true;
        }
        for (String[] group : TYPE_GROUPS) {
            if (ArrayUtils.contains(group, type1) && ArrayUtils.contains(group, type2)) {
                return true;
            }
        }
        return false;
    }

    public ModelWrapper(ModelDO modelDO) {
        this.modelDO = modelDO;
    }

    private ModelDO modelDO;

    public static final class Field {
        private String code = "";
        private String name = "";
        private String type = "";

        private String dictType = "";
        @Nullable
        private Integer precision;
        @Nullable
        private Integer dictTypeId;

        public String getCode() {
            return code;
        }

        public void setCode(String code) {
            this.code = code;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getDictType() {
            return dictType;
        }

        public void setDictType(String dictType) {
            this.dictType = dictType;
        }

        @Nullable
        public Integer getPrecision() {
            return precision;
        }

        public void setPrecision(@Nullable Integer precision) {
            this.precision = precision;
        }

        @Nullable
        public Integer getDictTypeId() {
            return dictTypeId;
        }

        public void setDictTypeId(@Nullable Integer dictTypeId) {
            this.dictTypeId = dictTypeId;
        }
    }

    public interface CustomHandle {
        /**
         * 处理自定义字段
         *
         * @param name  字段名称
         * @param value 字段值
         * @param field 字段属性
         */
        void execute(String name, String value, Field field);
    }

    public static class GetUrlsHandle implements CustomHandle {
        private final List<String> urls;

        public GetUrlsHandle(List<String> urls) {
            this.urls = urls;
        }

        @Override
        public void execute(String name, String value, Field field) {
            switch (field.getType()) {
                case TYPE_EDITOR:
                    urls.addAll(HtmlParserUtil.getUrls(value));
                    break;
                case TYPE_IMAGE_UPLOAD:
                case TYPE_VIDEO_UPLOAD:
                case TYPE_FILE_UPLOAD:
                    urls.add(value);
                    break;
                default:
            }
        }
    }

}
